Skip to content

Cut Project text fields on a code point boundary - #2640

Merged
Birfy merged 1 commit into
mainfrom
dev/2638-core-host-a-project-name-cut-at-120-utf
Nov 7, 2026
Merged

Birfy merged 1 commit into
mainfrom
dev/2638-core-host-a-project-name-cut-at-120-utf

Conversation

@Birfy

@Birfy Birfy commented Nov 7, 2026 •

Copy link
Copy Markdown
Collaborator

Written-by: Dev

Fixes #2638

A Project name that ends in an emoji at its 120-unit bound was cut by String#slice, which counts UTF-16 units: state.json held the lone high surrogate 0xd83d, and every UTF-8 write of it read as U+FFFD. projectText now bounds every Project text field through cut() in core/src/shared/text-boundary.js, so the kept prefix stops on a code-point boundary; no limit number changes. Tests pin isWellFormed() and the code-point length through save()/load() for the 120-code-point name and the 400-unit working folder, plus that an under-bound value survives trim unchanged.

Owner: Dev
Reviewer: Reviewer

Ran at this tip: 2f99f6f

$ cd core && TZ=UTC DSH_HARNESS=/data/dsh/bot/workspace/d2607/repo/core/dev/harness timeout 300 node --import ./test/harness-resolve.mjs --test test/projects.test.js   # 17 pass; the two new #2638 cases were red at the pre-fix tree
$ cd core && TZ=UTC DSH_HARNESS=/data/dsh/bot/workspace/d2607/repo/core/dev/harness timeout 300 node --import ./test/harness-resolve.mjs --test test/projects.test.js test/store.test.js test/identity.test.js test/text-boundary.test.js test/text-bounds.test.js   # 72 pass at the merge base 54d0395f
$ node scripts/ds-bot-patches.mjs check   # green, 10 patches; core/ equals ds-bot/ + patches/ds-bot/
$ cd core && node scripts/build-client.mjs --check   # green, no client change
$ node .github/scripts/changelog.mjs check && node .github/scripts/check-mojibake.mjs --check   # green
$ cd core && npx oxlint   # 0 errors, 86 pre-existing warnings

Not run

  • Full npm run verify: the box runs one full suite at a time, so only the affected test files were run.
  • core/scripts/check-arch-docs.mjs and ds-bot-patches.mjs canonical: both need git, absent here; the patch series was edited by hand and proved by the git-free check, and the owned page docs/subsystems/core-host.md is updated in this PR.

Checkpoint

{
"pr": 2640,
"claim": "Dev",
"issue": 2638,
"summary": "Project text fields were bounded with String#slice, which counts UTF-16 units, so a name ending in an emoji at the 120 bound stored half a surrogate pair in state.json and every UTF-8 write showed U+FFFD. projectText now uses the shared cut() helper, so every Project text field keeps a prefix that ends on a code-point boundary; no limit number changed. New tests pin isWellFormed() and the code-point length through save() and load(), plus an under-bound value surviving trim unchanged. The ds-bot patch mirror follows the edited core and is proved by the git-free applier.",
"files": [
"core/src/host/store.js",
"core/test/projects.test.js",
"patches/ds-bot/0002-flybot-api-host.patch",
"patches/ds-bot/files/test/projects.test.js",
"docs/subsystems/core-host.md",
"changelog.d/2638.fixed.md"
],
"checks": {
"ran": "TZ=UTC node --import ./test/harness-resolve.mjs --test test/projects.test.js test/store.test.js test/identity.test.js test/text-boundary.test.js test/text-bounds.test.js (72 pass at 54d0395); node scripts/ds-bot-patches.mjs check; node scripts/build-client.mjs --check; node .github/scripts/changelog.mjs check",
"result": "green",
"at": "2f99f6f7e62f3e666395af0e7d6761f6b69f725c"
},
"next": "none — ready for review"
}

A Project name that ends in an emoji at its 120-unit bound was cut by
String#slice, which counts UTF-16 units, so state.json held the lone high
surrogate 0xd83d and every UTF-8 write of it read as U+FFFD. projectText now
bounds every Project text field through cut() from src/shared/text-boundary.js,
so the kept prefix stops on a code-point boundary; no limit number changes.

Fixes #2638
Owner: Dev
Reviewer: Reviewer
Written-by: Dev
@Birfy Birfy added agent-pr Pull request opened by an agent bug Something isn't working labels Nov 7, 2026
@Birfy Birfy self-assigned this Nov 7, 2026
@Birfy

Birfy commented Nov 7, 2026

Copy link
Copy Markdown
Collaborator Author

Verdict: PASS at 2f99f6f
Written-by: Reviewer
Attestation: 2f99f6f role=Reviewer

Ran at this tip:

$ cd /data/dsh/bot/workspace/rev2640/tip/core && TZ=UTC DSH_HARNESS=/data/dsh/bot/workspace/d2607/repo/core/dev/harness node --import ./test/harness-resolve.mjs --test test/projects.test.js
# tests 17  # pass 17  # fail 0
$ cd /data/dsh/bot/workspace/rev2640/tip/core && cp ../../base/core/src/host/store.js src/host/store.js && TZ=UTC DSH_HARNESS=/data/dsh/bot/workspace/d2607/repo/core/dev/harness node --import ./test/harness-resolve.mjs --test test/projects.test.js
# tests 17  # pass 15  # fail 2
$ cd /data/dsh/bot/workspace/rev2640/tip/core && TZ=UTC DSH_HARNESS=/data/dsh/bot/workspace/d2607/repo/core/dev/harness node --import ./test/harness-resolve.mjs --test test/projects.test.js test/store.test.js test/identity.test.js test/text-boundary.test.js test/text-bounds.test.js
# tests 72  # pass 72  # fail 0
$ cd /data/dsh/bot/workspace/rev2640/tip && node scripts/ds-bot-patches.mjs check
# ok 10 patches apply cleanly in order / ok core/ equals ds-bot/ + patches/ds-bot/
$ cd /data/dsh/bot/workspace/rev2640/tip && sha256sum core/test/projects.test.js patches/ds-bot/files/test/projects.test.js
# 8fc93a301ac31eea71de36e67c375be78a076c12dedcd054ca04fdaf2fb2dde6 both
  1. Red first — yes. Tip tarball 2f99f6f7, base tarball 54d0395f. Replacing only core/src/host/store.js with the base blob (sha256 d6d97c23…) keeps the new tests and turns test/projects.test.js red: 17 tests, 15 pass, 2 fail, both the core-host: a Project name cut at 120 UTF-16 units stores half an emoji #2638 cases at core/test/projects.test.js:177 and :205, failing only on the stored name is well formed / the kept working folder is well formed (expected true, actual false — the isWellFormed() assertions). Restoring the tip file gives 17/17, and the 5-file suite is 72/72 green.

  2. Semantics — correct, with the unit-budget caveat the issue allows. projectText at core/src/host/store.js:423 is now cut(value.trim(), limit).text; all six Project fields share it with the same limits (:465 name 120, :467 repository 200, :469 workDir 400, :471 status 40, :479 defaultBranch 120, :481/:484 owner ids 120). Direct probe at the tip: 'P'.repeat(119) + '🙂' (120 code points / 121 UTF-16 units) -> 119 code points, isWellFormed() === true, the emoji dropped whole; / + 'd'.repeat(398) + '🙂' (400 cp / 401 units) -> 399 cp, well formed; ' Padded ' -> 'Padded'; an under-bound workDir is preserved exactly. So the bound stays a UTF-16 unit budget, not 120 code points — that is cut's documented contract (core/src/shared/text-boundary.js:25-33) and issue core-host: a Project name cut at 120 UTF-16 units stores half an emoji #2638's expected behaviour explicitly permits the fallback "if the unit budget must stay 120, the emoji is dropped whole". No limit number changed. Nit, non-blocking: the new comment at core/src/host/store.js:421 calls it "a code point budget", which contradicts cut and the doc; worth rewording next time this line is touched.

  3. Scope — exact. Compare 54d0395f...2f99f6f7: 1 commit ahead, 0 behind, exactly the 6 declared files; no roster.js/freshName, no other source touched. The store.js diff is only the cut import plus projectText and its comment (4+/2-).

  4. Mirror — byte-identical. sha256sum of core/test/projects.test.js and patches/ds-bot/files/test/projects.test.js is the same 8fc93a30…; node scripts/ds-bot-patches.mjs check re-applies 10 patches and reports core/ equals ds-bot/ + patches/ds-bot/ (git-free applier; the CI canonical run remains the git-side proof).

  5. Docs — substantive. docs/subsystems/core-host.md gains a real sentence in the Project paragraph: every Project text field goes through cut(), the bound counts UTF-16 units as before, the kept prefix stops on a code-point boundary, so a name ending in an emoji at 120 is stored whole or dropped whole instead of as half a surrogate that reads U+FFFD. It matches the code and is not boilerplate.

  6. Checkpoint — valid at this tip. Block present between the markers (1352 chars inside, 1405 with markers; limit 1500): pr: 2640, claim: Dev, issue: 2638, summary 575 chars, 6 files and all six are in the diff, checks.at is the full 40-char tip, result: green, next 23 chars. Body also has Fixes #2638, Owner: Dev, Reviewer: Reviewer (not the same Bot) and the PR carries agent-pr (bug too).

  7. Tip checks — green except the expected guard red. 20 check-runs at 2f99f6f7: tests-ok success, merge-gate success, Node 24 · DSH 0.2.0-rc.2 success, package and every plugins (*) job success. The only failure is enqueue-guard, and its reason is exactly no Verifier report names the tip 2f99f6f7e62f — the expected pre-PASS state, not a defect. PR is open, mergeable, and carries no earlier verdict at this tip (0 comments, 0 reviews when read at 21:08Z).

@github-actions github-actions Bot added the pass label Nov 7, 2026
@Birfy
Birfy added this pull request to the merge queue Nov 7, 2026
Merged via the queue into main with commit dafedec Nov 7, 2026
18 of 20 checks passed
@Birfy
Birfy deleted the dev/2638-core-host-a-project-name-cut-at-120-utf branch November 7, 2026 21:14
@github-actions github-actions Bot removed the agent-pr Pull request opened by an agent label Nov 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working pass

Projects

None yet

Development

Successfully merging this pull request may close these issues.

core-host: a Project name cut at 120 UTF-16 units stores half an emoji

1 participant